package com.cc.blog.web.service.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cc.blog.admin.event.ReadArticleEvent;
import com.cc.blog.admin.mapper.*;
import com.cc.blog.admin.model.*;
import com.cc.blog.common.enums.CodeEnum;
import com.cc.blog.common.exception.BusinessException;
import com.cc.blog.common.model.BaseDO;
import com.cc.blog.common.utils.PageResult;
import com.cc.blog.common.utils.Result;
import com.cc.blog.web.convert.ArticleConvert;
import com.cc.blog.web.domain.dto.FindArticleDetailReqDTO;
import com.cc.blog.web.domain.dto.FindIndexArticlePageListReqDTO;
import com.cc.blog.web.domain.vo.*;
import com.cc.blog.web.markdown.MarkdownHelper;
import com.cc.blog.web.service.ArticleService;
import com.cc.blog.web.utils.MarkdownStatsUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @Author cc
 * @Date 2024/7/12 9:53
 */
@Service
@Slf4j
@AllArgsConstructor
public class ArticleServiceImpl implements ArticleService {

    private final ArticleMapper articleMapper;
    private final CategoryMapper categoryMapper;
    private final ArticleCategoryRelMapper articleCategoryRelMapper;
    private final TagMapper tagMapper;
    private final ArticleTagRelMapper articleTagRelMapper;
    private final ArticleContentMapper articleContentMapper;
    private final ApplicationEventPublisher eventPublisher;

    @Override
    public Result findArticlePageList(FindIndexArticlePageListReqDTO findIndexArticlePageListReqDTO) {
        Long current = findIndexArticlePageListReqDTO.getCurrent();
        Long size = findIndexArticlePageListReqDTO.getSize();
        // 第一步：分页查询文章主体记录
        Page<ArticleDO> articleDOPage = articleMapper.selectPage(new Page<>(current,size), Wrappers.<ArticleDO>lambdaQuery().orderByDesc(BaseDO::getCreateTime));
        // 返回的分页数据
        List<ArticleDO> articleDOS = articleDOPage.getRecords();
        List<FindIndexArticlePageListRspVO> vos = null;
        if (!CollectionUtils.isEmpty(articleDOS)) {
            // 文章 DO 转 VO
            vos = articleDOS.stream()
                    .map(ArticleConvert.INSTANCE::convertDO2VO)
                    .collect(Collectors.toList());
            // 拿到所有文章的 ID 集合
            List<Long> articleIds = articleDOS.stream().map(ArticleDO::getId).collect(Collectors.toList());
            // 第二步：设置文章所属分类
            // 查询所有分类
            List<CategoryDO> categoryDOS = categoryMapper.selectList(Wrappers.emptyWrapper());
            // 转 Map, 方便后续根据分类 ID 拿到对应的分类名称
            Map<Long, String> categoryIdNameMap = categoryDOS.stream().collect(Collectors.toMap(CategoryDO::getId, CategoryDO::getName));
            // 根据文章 ID 批量查询所有关联记录
            List<ArticleCategoryRelDO> articleCategoryRelDOS = articleCategoryRelMapper.selectList(
                    Wrappers.<ArticleCategoryRelDO>lambdaQuery().in(ArticleCategoryRelDO::getArticleId, articleIds));
            vos.forEach(vo -> {
                Long currArticleId = vo.getId();
                // 过滤出当前文章对应的关联数据
                Optional<ArticleCategoryRelDO> optional = articleCategoryRelDOS.stream().filter(rel -> Objects.equals(rel.getArticleId(), currArticleId)).findAny();
                // 若不为空
                if (optional.isPresent()) {
                    ArticleCategoryRelDO articleCategoryRelDO = optional.get();
                    Long categoryId = articleCategoryRelDO.getCategoryId();
                    // 通过分类 ID 从 map 中拿到对应的分类名称
                    String categoryName = categoryIdNameMap.get(categoryId);
                    FindCategoryListRspVO findCategoryListRspVO = FindCategoryListRspVO.builder()
                            .id(categoryId)
                            .name(categoryName)
                            .build();
                    // 设置到当前 vo 类中
                    vo.setCategory(findCategoryListRspVO);
                }
            });
            // 第三步：设置文章标签
            // 查询所有标签
            List<TagDO> tagDOS = tagMapper.selectList(Wrappers.emptyWrapper());
            // 转 Map, 方便后续根据标签 ID 拿到对应的标签名称
            Map<Long, String> mapIdNameMap = tagDOS.stream().collect(Collectors.toMap(TagDO::getId, TagDO::getName));
            // 拿到所有文章的标签关联记录
            List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectList(
                    Wrappers.<ArticleTagRelDO>lambdaQuery().in(ArticleTagRelDO::getArticleId, articleIds));
            vos.forEach(vo -> {
                Long currArticleId = vo.getId();
                // 过滤出当前文章的标签关联记录
                List<ArticleTagRelDO> articleTagRelDOList = articleTagRelDOS.stream().filter(rel -> Objects.equals(rel.getArticleId(), currArticleId)).collect(Collectors.toList());
                List<FindTagListRspVO> findTagListRspVOS = Lists.newArrayList();
                // 将关联记录 DO 转 VO, 并设置对应的标签名称
                articleTagRelDOList.forEach(articleTagRelDO -> {
                    Long tagId = articleTagRelDO.getTagId();
                    String tagName = mapIdNameMap.get(tagId);
                    FindTagListRspVO findTagListRspVO = FindTagListRspVO.builder()
                            .id(tagId)
                            .name(tagName)
                            .build();
                    findTagListRspVOS.add(findTagListRspVO);
                });
                // 设置转换后的标签数据
                vo.setTags(findTagListRspVOS);
            });
        }
        return PageResult.success(articleDOPage, vos);
    }

    @Override
    public Result findArticleDetail(FindArticleDetailReqDTO findArticleDetailReqDTO) {
        Long articleId = findArticleDetailReqDTO.getArticleId();
        ArticleDO articleDO = articleMapper.selectById(articleId);
        // 判断文章是否存在
        if (Objects.isNull(articleDO)) {
            log.warn("==> 该文章不存在, articleId: {}", articleId);
            throw new BusinessException(CodeEnum.ARTICLE_NOT_FOUND);
        }
        // 查询正文
        ArticleContentDO articleContentDO = articleContentMapper.selectOne(Wrappers.<ArticleContentDO>lambdaQuery().eq(ArticleContentDO::getArticleId,articleId));
        String content = articleContentDO.getContent();
        // 计算 md 正文字数
        Integer totalWords = MarkdownStatsUtil.calculateWordCount(content);
        // DO 转 VO
        FindArticleDetailRspVO vo = FindArticleDetailRspVO.builder()
                .title(articleDO.getTitle())
                .createTime(articleDO.getCreateTime())
                .content(MarkdownHelper.convertMarkdown2Html(articleContentDO.getContent()))
                .readNum(articleDO.getReadNum())
                .totalWords(totalWords)
                .readTime(MarkdownStatsUtil.calculateReadingTime(totalWords))
                .updateTime(articleDO.getUpdateTime())
                .build();
        // 查询所属分类
        ArticleCategoryRelDO articleCategoryRelDO = articleCategoryRelMapper.selectOne(
                Wrappers.<ArticleCategoryRelDO>lambdaQuery().eq(ArticleCategoryRelDO::getArticleId, articleId));
        CategoryDO categoryDO = categoryMapper.selectById(articleCategoryRelDO.getCategoryId());
        vo.setCategoryId(categoryDO.getId());
        vo.setCategoryName(categoryDO.getName());
        // 查询标签
        List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectList(
                Wrappers.<ArticleTagRelDO>lambdaQuery().eq(ArticleTagRelDO::getArticleId, articleId));
        List<Long> tagIds = articleTagRelDOS.stream().map(ArticleTagRelDO::getTagId).collect(Collectors.toList());
        List<TagDO> tagDOS = tagMapper.selectBatchIds(tagIds);
        // 标签 DO 转 VO
        List<FindTagListRspVO> tagVOS = tagDOS.stream()
                .map(tagDO -> FindTagListRspVO.builder().id(tagDO.getId()).name(tagDO.getName()).build())
                .collect(Collectors.toList());
        vo.setTags(tagVOS);
        // 上一篇
        ArticleDO preArticleDO = articleMapper.selectOne(Wrappers.<ArticleDO>lambdaQuery()
                // 按文章 ID 倒序排列
                .orderByDesc(ArticleDO::getId)
                // 查询比当前文章 ID 大的
                .gt(ArticleDO::getId, articleId)
                .last("limit 1"));
        if (Objects.nonNull(preArticleDO)) {
            FindPreNextArticleRspVO preArticleVO = FindPreNextArticleRspVO.builder()
                    .articleId(preArticleDO.getId())
                    .articleTitle(preArticleDO.getTitle())
                    .build();
            vo.setPreArticle(preArticleVO);
        }
        // 下一篇
        ArticleDO nextArticleDO = articleMapper.selectOne(Wrappers.<ArticleDO>lambdaQuery()
                // 按文章 ID 倒序排列
                .orderByDesc(ArticleDO::getId)
                // 查询比当前文章 ID 小的
                .lt(ArticleDO::getId, articleId)
                .last("limit 1"));
        if (Objects.nonNull(nextArticleDO)) {
            FindPreNextArticleRspVO nextArticleVO = FindPreNextArticleRspVO.builder()
                    .articleId(nextArticleDO.getId())
                    .articleTitle(nextArticleDO.getTitle())
                    .build();
            vo.setNextArticle(nextArticleVO);
        }
        // 发布文章阅读事件
        eventPublisher.publishEvent(new ReadArticleEvent(this, articleId));
        return Result.success(vo);
    }
}
